 /*******************************************************************************
  * Copyright (c) 2005, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/

 package org.eclipse.osgi.internal.baseadaptor;

 import java.io.*;
 import java.lang.reflect.InvocationTargetException ;
 import java.lang.reflect.Method ;
 import java.net.*;
 import java.util.*;
 import org.eclipse.core.runtime.adaptor.EclipseStarter;
 import org.eclipse.core.runtime.adaptor.LocationManager;
 import org.eclipse.core.runtime.internal.adaptor.EclipseAdaptorMsg;
 import org.eclipse.osgi.baseadaptor.*;
 import org.eclipse.osgi.baseadaptor.bundlefile.*;
 import org.eclipse.osgi.baseadaptor.hooks.*;
 import org.eclipse.osgi.framework.adaptor.*;
 import org.eclipse.osgi.framework.debug.Debug;
 import org.eclipse.osgi.framework.debug.FrameworkDebugOptions;
 import org.eclipse.osgi.framework.internal.core.*;
 import org.eclipse.osgi.framework.internal.core.Constants;
 import org.eclipse.osgi.framework.log.FrameworkLogEntry;
 import org.eclipse.osgi.framework.util.KeyedHashSet;
 import org.eclipse.osgi.service.datalocation.Location;
 import org.eclipse.osgi.service.resolver.*;
 import org.eclipse.osgi.storagemanager.ManagedOutputStream;
 import org.eclipse.osgi.storagemanager.StorageManager;
 import org.eclipse.osgi.util.ManifestElement;
 import org.eclipse.osgi.util.NLS;
 import org.osgi.framework.*;

 public class BaseStorage implements SynchronousBundleListener {
     private static final String RUNTIME_ADAPTOR = FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME + "/eclipseadaptor"; //$NON-NLS-1$
 private static final String OPTION_PLATFORM_ADMIN = RUNTIME_ADAPTOR + "/debug/platformadmin"; //$NON-NLS-1$
 private static final String OPTION_PLATFORM_ADMIN_RESOLVER = RUNTIME_ADAPTOR + "/debug/platformadmin/resolver"; //$NON-NLS-1$
 private static final String OPTION_MONITOR_PLATFORM_ADMIN = RUNTIME_ADAPTOR + "/resolver/timing"; //$NON-NLS-1$
 private static final String OPTION_RESOLVER_READER = RUNTIME_ADAPTOR + "/resolver/reader/timing"; //$NON-NLS-1$
 private static final String PROP_FRAMEWORK_EXTENSIONS = "osgi.framework.extensions"; //$NON-NLS-1$
 private static final String PROP_BUNDLE_STORE = "osgi.bundlestore"; //$NON-NLS-1$
 // The name of the bundle data directory
 static final String DATA_DIR_NAME = "data"; //$NON-NLS-1$
 static final String LIB_TEMP = "libtemp"; //$NON-NLS-1$
 // System property used to determine whether State saver needs to be enabled
 private static final String PROP_ENABLE_STATE_SAVER = "eclipse.enableStateSaver"; //$NON-NLS-1$
 static final String BUNDLEFILE_NAME = "bundlefile"; //$NON-NLS-1$
 // System property used to clean the osgi configuration area
 private static final String PROP_CLEAN = "osgi.clean"; //$NON-NLS-1$

     /** The current bundle data version */
     public static final byte BUNDLEDATA_VERSION = 18;
     /**
      * flag to indicate a framework extension is being intialized
      */
     public static final byte EXTENSION_INITIALIZE = 0x01;
     /**
      * flag to indicate a framework extension is being installed
      */
     public static final byte EXTENSION_INSTALLED = 0x02;
     /**
      * flag to indicate a framework extension is being uninstalled
      */
     public static final byte EXTENSION_UNINSTALLED = 0x04;
     /**
      * flag to indicate a framework extension is being updated
      */
     public static final byte EXTENSION_UPDATED = 0x08;

     /**
      * the file name for the delete flag. If this file exists in one a directory
      * under the bundle store area then it will be removed during the
      * compact operation.
      */
     public static final String DELETE_FLAG = ".delete"; //$NON-NLS-1$
 private static final String PERM_DATA_FILE = ".permdata"; //$NON-NLS-1$
 private static final byte PERMDATA_VERSION = 1;

     private BaseAdaptor adaptor;
     // assume a file: installURL
 private String installPath;
     private StorageManager storageManager;
     private StateManager stateManager;
     // no need to synchronize on storageHooks because the elements are statically set in initialize
 private KeyedHashSet storageHooks = new KeyedHashSet(5, false);
     private BundleContext context;
     private SynchronousBundleListener extensionListener;

     /**
      * The add URL method used to support framework extensions
      */
     private Method addURLMethod;
     /**
      * The list of configured framework extensions
      */
     private String [] configuredExtensions;

     private long timeStamp = 0;
     private int initialBundleStartLevel = 1;
     private long nextId = 1;
     /**
      * directory containing installed bundles
      */
     private File bundleStoreRoot;

     private BasePermissionStorage permissionStorage;
     private StateSaver stateSaver;
     private boolean invalidState;
     private boolean storageManagerClosed;

     BaseStorage() {
         // make constructor package private
 }

     public void initialize(BaseAdaptor adaptor) throws IOException {
         this.adaptor = adaptor;
         setDebugOptions();
         if (Boolean.valueOf(FrameworkProperties.getProperty(BaseStorage.PROP_CLEAN)).booleanValue())
             cleanOSGiCache();
         // initialize the addURLMethod to support framework extensions
 ClassLoader fwloader = BaseStorage.class.getClassLoader();
         if (fwloader != null)
             addURLMethod = findaddURLMethod(fwloader.getClass());
         // we need to set the install path as soon as possible so we can determine
 // the absolute location of install relative URLs
 Location installLoc = LocationManager.getInstallLocation();
         if (installLoc != null) {
             URL installURL = installLoc.getURL();
             // assume install URL is file: based
 installPath = installURL.getPath();
         }
         boolean readOnlyConfiguration = LocationManager.getConfigurationLocation().isReadOnly();
         storageManager = initFileManager(LocationManager.getOSGiConfigurationDir(), readOnlyConfiguration ? "none" : null, readOnlyConfiguration); //$NON-NLS-1$
 storageManagerClosed = false;
         // initialize the storageHooks
 StorageHook[] hooks = adaptor.getHookRegistry().getStorageHooks();
         for (int i = 0; i < hooks.length; i++)
             storageHooks.add(hooks[i]);
     }

     private void setDebugOptions() {
         FrameworkDebugOptions options = FrameworkDebugOptions.getDefault();
         // may be null if debugging is not enabled
 if (options == null)
             return;
         StateManager.DEBUG = options != null;
         StateManager.DEBUG_READER = options.getBooleanOption(OPTION_RESOLVER_READER, false);
         StateManager.MONITOR_PLATFORM_ADMIN = options.getBooleanOption(OPTION_MONITOR_PLATFORM_ADMIN, false);
         StateManager.DEBUG_PLATFORM_ADMIN = options.getBooleanOption(OPTION_PLATFORM_ADMIN, false);
         StateManager.DEBUG_PLATFORM_ADMIN_RESOLVER = options.getBooleanOption(OPTION_PLATFORM_ADMIN_RESOLVER, false);
     }

     protected StorageManager initFileManager(File baseDir, String lockMode, boolean readOnly) {
         StorageManager sManager = new StorageManager(baseDir, lockMode, readOnly);
         try {
             sManager.open(!readOnly);
         } catch (IOException ex) {
             if (Debug.DEBUG && Debug.DEBUG_GENERAL) {
                 Debug.println("Error reading framework metadata: " + ex.getMessage()); //$NON-NLS-1$
 Debug.printStackTrace(ex);
             }
             String message = NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_FILEMANAGER_OPEN_ERROR, ex.getMessage());
             FrameworkLogEntry logEntry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, message, 0, ex, null);
             adaptor.getFrameworkLog().log(logEntry);
         }
         return sManager;
     }

     public boolean isReadOnly() {
         return storageManager.isReadOnly();
     }

     public void compact() throws IOException {
         if (!isReadOnly())
             compact(getBundleStoreRoot());
     }

     private void compact(File directory) {
         if (Debug.DEBUG && Debug.DEBUG_GENERAL)
             Debug.println("compact(" + directory.getPath() + ")"); //$NON-NLS-1$ //$NON-NLS-2$
 String list[] = directory.list();
         if (list == null)
             return;

         int len = list.length;
         for (int i = 0; i < len; i++) {
             if (BaseStorage.DATA_DIR_NAME.equals(list[i]))
                 continue; /* do not examine the bundles data dir. */
             File target = new File(directory, list[i]);
             // if the file is a directory
 if (!target.isDirectory())
                 continue;
             File delete = new File(target, BaseStorage.DELETE_FLAG);
             // and the directory is marked for delete
 if (delete.exists()) {
                 // if rm fails to delete the directory and .delete was removed
 if (!AdaptorUtil.rm(target) && !delete.exists()) {
                     try {
                         // recreate .delete
 FileOutputStream out = new FileOutputStream(delete);
                         out.close();
                     } catch (IOException e) {
                         if (Debug.DEBUG && Debug.DEBUG_GENERAL)
                             Debug.println("Unable to write " + delete.getPath() + ": " + e.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$
 }
                 }
             } else {
                 compact(target); /* descend into directory */
             }
         }
     }

     public long getFreeSpace() throws IOException {
         // cannot implement this without native code!
 return -1;
     }

     public File getDataFile(BaseData data, String path) {
         BaseStorageHook storageHook = (BaseStorageHook) data.getStorageHook(BaseStorageHook.KEY);
         if (storageHook == null)
             return null;
         return storageHook.getDataFile(path);
     }

     BaseAdaptor getAdaptor() {
         return adaptor;
     }

     public void installNativeCode(BaseData data, String [] nativepaths) throws BundleException {
         if (nativepaths.length > 0) {
             BaseStorageHook storageHook = (BaseStorageHook) data.getStorageHook(BaseStorageHook.KEY);
             if (storageHook != null)
                 storageHook.installNativePaths(nativepaths);
         }
     }

     public Dictionary loadManifest(BaseData data) throws BundleException {
         return loadManifest(data, false);
     }

     public Dictionary loadManifest(BaseData bundleData, boolean firstTime) throws BundleException {
         Dictionary result = null;
         StorageHook[] dataStorageHooks = bundleData.getStorageHooks();
         for (int i = 0; i < dataStorageHooks.length && result == null; i++)
             result = dataStorageHooks[i].getManifest(firstTime);
         if (result == null)
             result = AdaptorUtil.loadManifestFrom(bundleData);
         if (result == null)
             throw new BundleException(NLS.bind(AdaptorMsg.MANIFEST_NOT_FOUND_EXCEPTION, Constants.OSGI_BUNDLE_MANIFEST, bundleData.getLocation()));
         return result;
     }

     public File getExtractFile(BaseData data, String path) {
         BaseStorageHook storageHook = (BaseStorageHook) data.getStorageHook(BaseStorageHook.KEY);
         if (storageHook == null)
             return null;
         // first check the child generation dir
 File childGenDir = storageHook.getGenerationDir();
         if (childGenDir != null) {
             File childPath = new File(childGenDir, path);
             if (childPath.exists())
                 return childPath;
         }
         // now check the parent
 File parentGenDir = storageHook.getParentGenerationDir();
         if (parentGenDir != null) {
             // there is a parent generation check if the file exists
 File parentPath = new File(parentGenDir, path);
             if (parentPath.exists())
                 // only use the parent generation file if it exists; do not extract there
 return parentPath;
         }
         // did not exist in both locations; create a file for extraction.
 File bundleGenerationDir = storageHook.createGenerationDir();
         /* if the generation dir exists, then we have place to cache */
         if (bundleGenerationDir != null && bundleGenerationDir.exists())
             return new File(bundleGenerationDir, path);
         return null;
     }

     public BaseData[] getInstalledBundles() {
         return readBundleDatas();
     }

     private BaseData[] readBundleDatas() {
         InputStream bundleDataStream = findStorageStream(LocationManager.BUNDLE_DATA_FILE);
         if (bundleDataStream == null)
             return null;
         try {
             DataInputStream in = new DataInputStream(new BufferedInputStream(bundleDataStream));
             try {
                 byte version = in.readByte();
                 if (version != BUNDLEDATA_VERSION)
                     return null;
                 timeStamp = in.readLong();
                 initialBundleStartLevel = in.readInt();
                 nextId = in.readLong();

                 int numStorageHooks = in.readInt();
                 StorageHook[] storageHooks = adaptor.getHookRegistry().getStorageHooks();
                 if (numStorageHooks != storageHooks.length)
                     return null; // must have the same number of storagehooks to properly read the data
 for (int i = 0; i < numStorageHooks; i++) {
                     Object storageKey = storageHooks[i].getKey();
                     int storageVersion = storageHooks[i].getStorageVersion();
                     if (!storageKey.equals(in.readUTF()) || storageVersion != in.readInt())
                         return null; // some storage hooks have changed must throw the data away.
 }

                 int bundleCount = in.readInt();
                 ArrayList result = new ArrayList(bundleCount);
                 long id = -1;
                 boolean bundleDiscarded = false;
                 for (int i = 0; i < bundleCount; i++) {
                     boolean error = false;
                     BaseData data = null;
                     try {
                         id = in.readLong();
                         if (id != 0) {
                             data = loadBaseData(id, in);
                             data.getBundleFile();
                             StorageHook[] dataStorageHooks = data.getStorageHooks();
                             for (int j = 0; j < dataStorageHooks.length; j++)
                                 dataStorageHooks[j].validate();
                             if (Debug.DEBUG && Debug.DEBUG_GENERAL)
                                 Debug.println("BundleData created: " + data); //$NON-NLS-1$
 processExtension(data, EXTENSION_INITIALIZE);
                             result.add(data);
                         }
                     } catch (IllegalArgumentException e) {
                         // may be from data.getBundleFile()
 bundleDiscarded = true;
                         error = true;
                     } catch (BundleException e) {
                         // should never happen
 bundleDiscarded = true;
                         error = true;
                     } catch (IOException e) {
                         bundleDiscarded = true;
                         error = true;
                         if (Debug.DEBUG && Debug.DEBUG_GENERAL) {
                             Debug.println("Error reading framework metadata: " + e.getMessage()); //$NON-NLS-1$
 Debug.printStackTrace(e);
                         }
                     }
                     if (error && data != null) {
                         BaseStorageHook storageHook = (BaseStorageHook) data.getStorageHook(BaseStorageHook.KEY);
                         storageHook.delete(true, BaseStorageHook.DEL_BUNDLE_STORE);
                     }
                 }
                 if (bundleDiscarded)
                     FrameworkProperties.setProperty(EclipseStarter.PROP_REFRESH_BUNDLES, "true"); //$NON-NLS-1$
 return (BaseData[]) result.toArray(new BaseData[result.size()]);
             } finally {
                 in.close();
             }
         } catch (IOException e) {
             if (Debug.DEBUG && Debug.DEBUG_GENERAL) {
                 Debug.println("Error reading framework metadata: " + e.getMessage()); //$NON-NLS-1$
 Debug.printStackTrace(e);
             }
         }
         return null;
     }

     private void saveAllData(boolean shutdown) {
         if (storageManagerClosed)
             try {
                 storageManager.open(!LocationManager.getConfigurationLocation().isReadOnly());
                 storageManagerClosed = false;
             } catch (IOException e) {
                 String message = NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_FILEMANAGER_OPEN_ERROR, e.getMessage());
                 FrameworkLogEntry logEntry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, message, 0, e, null);
                 adaptor.getFrameworkLog().log(logEntry);
             }
         saveBundleDatas();
         saveStateData(shutdown);
         savePermissionStorage();
         if (shutdown)
             stateManager.stopDataManager();
     }

     private BasePermissionStorage readPermissionData() {
         BasePermissionStorage result = new BasePermissionStorage(this);
         InputStream permDataStream = findStorageStream(PERM_DATA_FILE);
         if (permDataStream == null)
             return result;
         try {
             DataInputStream in = new DataInputStream(new BufferedInputStream(permDataStream));
             try {
                 if (PERMDATA_VERSION != in.readByte())
                     return result;
                 // read the default permissions first
 int numPerms = in.readInt();
                 if (numPerms > 0) {
                     String [] perms = new String [numPerms];
                     for (int i = 0; i < numPerms; i++)
                         perms[i] = in.readUTF();
                     result.setPermissionData(null, perms);
                 }
                 int numLocs = in.readInt();
                 if (numLocs > 0)
                     for (int i = 0; i < numLocs; i++) {
                         String loc = in.readUTF();
                         numPerms = in.readInt();
                         String [] perms = new String [numPerms];
                         for (int j = 0; j < numPerms; j++)
                             perms[j] = in.readUTF();
                         result.setPermissionData(loc, perms);
                     }
                 int numCondPerms = in.readInt();
                 if (numCondPerms > 0) {
                     String [] condPerms = new String [numCondPerms];
                     for (int i = 0; i < numCondPerms; i++)
                         condPerms[i] = in.readUTF();
                     result.saveConditionalPermissionInfos(condPerms);
                 }
                 result.setDirty(false);
             } finally {
                 in.close();
             }
         } catch (IOException e) {
             adaptor.getFrameworkLog().log(new FrameworkEvent(FrameworkEvent.ERROR, context.getBundle(), e));
         }
         return result;
     }

     private void savePermissionStorage() {
         if (permissionStorage == null || isReadOnly() || !permissionStorage.isDirty())
             return;
         try {
             ManagedOutputStream fmos = storageManager.getOutputStream(PERM_DATA_FILE);
             DataOutputStream out = new DataOutputStream(new BufferedOutputStream(fmos));
             boolean error = true;
             try {
                 out.writeByte(PERMDATA_VERSION);
                 // always write the default permissions first
 String [] defaultPerms = permissionStorage.getPermissionData(null);
                 out.writeInt(defaultPerms == null ? 0 : defaultPerms.length);
                 if (defaultPerms != null)
                     for (int i = 0; i < defaultPerms.length; i++)
                         out.writeUTF(defaultPerms[i]);
                 String [] locations = permissionStorage.getLocations();
                 out.writeInt(locations == null ? 0 : locations.length);
                 if (locations != null)
                     for (int i = 0; i < locations.length; i++) {
                         out.writeUTF(locations[i]);
                         String [] perms = permissionStorage.getPermissionData(locations[i]);
                         out.writeInt(perms == null ? 0 : perms.length);
                         if (perms != null)
                             for (int j = 0; j < perms.length; j++)
                                 out.writeUTF(perms[j]);
                     }
                 String [] condPerms = permissionStorage.getConditionalPermissionInfos();
                 out.writeInt(condPerms == null ? 0 : condPerms.length);
                 if (condPerms != null)
                     for (int i = 0; i < condPerms.length; i++)
                         out.writeUTF(condPerms[i]);
                 out.close();
                 permissionStorage.setDirty(false);
                 error = false;
             } finally {
                 // if something happens, don't close a corrupt file
 if (error) {
                     fmos.abort();
                     try {
                         out.close();
                     } catch (IOException e) {/*ignore*/
                     }
                 }
             }
         } catch (IOException e) {
             adaptor.getFrameworkLog().log(new FrameworkEvent(FrameworkEvent.ERROR, context.getBundle(), e));
             return;
         }
     }

     private void saveBundleDatas() {
         // the cache and the state match
 if (stateManager == null || isReadOnly() || (timeStamp == stateManager.getSystemState().getTimeStamp() && !stateManager.saveNeeded()))
             return;
         try {
             ManagedOutputStream fmos = storageManager.getOutputStream(LocationManager.BUNDLE_DATA_FILE);
             DataOutputStream out = new DataOutputStream(new BufferedOutputStream(fmos));
             boolean error = true;
             try {
                 out.writeByte(BUNDLEDATA_VERSION);
                 out.writeLong(stateManager.getSystemState().getTimeStamp());
                 out.writeInt(initialBundleStartLevel);
                 out.writeLong(nextId);

                 StorageHook[] storageHooks = adaptor.getHookRegistry().getStorageHooks();
                 out.writeInt(storageHooks.length);
                 for (int i = 0; i < storageHooks.length; i++) {
                     out.writeUTF((String ) storageHooks[i].getKey());
                     out.writeInt(storageHooks[i].getStorageVersion());
                 }

                 Bundle[] bundles = context.getBundles();
                 out.writeInt(bundles.length);
                 for (int i = 0; i < bundles.length; i++) {
                     long id = bundles[i].getBundleId();
                     out.writeLong(id);
                     if (id != 0) {
                         BundleData data = ((org.eclipse.osgi.framework.internal.core.AbstractBundle) bundles[i]).getBundleData();
                         saveBaseData((BaseData) data, out);
                     }
                 }
                 out.close();
                 // update the 'timeStamp' after the changed Meta data is saved.
 timeStamp = stateManager.getSystemState().getTimeStamp();
                 error = false;
             } finally {
                 // if something happens, don't close a corrupt file
 if (error) {
                     fmos.abort();
                     try {
                         out.close();
                     } catch (IOException e) {/*ignore*/
                     }
                 }
             }
         } catch (IOException e) {
             adaptor.getFrameworkLog().log(new FrameworkEvent(FrameworkEvent.ERROR, context.getBundle(), e));
             return;
         }
     }

     private void saveStateData(boolean shutdown) {
         if (stateManager == null || isReadOnly() || !stateManager.saveNeeded())
             return;
         try {
             File stateTmpFile = File.createTempFile(LocationManager.STATE_FILE, ".new", LocationManager.getOSGiConfigurationDir()); //$NON-NLS-1$
 File lazyTmpFile = File.createTempFile(LocationManager.LAZY_FILE, ".new", LocationManager.getOSGiConfigurationDir()); //$NON-NLS-1$
 if (shutdown)
                 stateManager.shutdown(stateTmpFile, lazyTmpFile);
             else
                 synchronized (stateManager) {
                     stateManager.update(stateTmpFile, lazyTmpFile);
                 }
             storageManager.lookup(LocationManager.STATE_FILE, true);
             storageManager.lookup(LocationManager.LAZY_FILE, true);
             storageManager.update(new String [] {LocationManager.STATE_FILE, LocationManager.LAZY_FILE}, new String [] {stateTmpFile.getName(), lazyTmpFile.getName()});
         } catch (IOException e) {
             adaptor.getFrameworkLog().log(new FrameworkEvent(FrameworkEvent.ERROR, context.getBundle(), e));
         }
     }

     public PermissionStorage getPermissionStorage() throws IOException {
         if (permissionStorage == null)
             permissionStorage = readPermissionData();
         return permissionStorage;
     }

     public int getInitialBundleStartLevel() {
         return initialBundleStartLevel;
     }

     public void setInitialBundleStartLevel(int value) {
         this.initialBundleStartLevel = value;
         requestSave();
     }

     public void save(BaseData data) throws IOException {
         if (data.isDirty()) {
             timeStamp--; // Change the value of the timeStamp, as a marker that something changed.
 requestSave();
             data.setDirty(false);
         }
     }

     public BundleOperation installBundle(String location, URLConnection source) {
         BaseData data = createBaseData(getNextBundleId(), location);
         return new BundleInstall(data, source, this);
     }

     public BundleOperation updateBundle(BaseData data, URLConnection source) {
         return new BundleUpdate(data, source, this);
     }

     public BundleOperation uninstallBundle(BaseData data) {
         return new BundleUninstall(data, this);
     }

     protected Object getBundleContent(BaseData bundledata) throws IOException {
         BaseStorageHook storageHook = (BaseStorageHook) bundledata.getStorageHook(BaseStorageHook.KEY);
         if (storageHook == null)
             throw new IllegalStateException ();
         return storageHook.isReference() ? new File(storageHook.getFileName()) : new File(storageHook.getGenerationDir(), storageHook.getFileName());
     }

     public BundleFile createBundleFile(Object content, BaseData data) throws IOException {
         boolean base = false;
         if (content == null) {
             // this must be a request for the base bundlefile
 base = true;
             // get the content of this bundle
 content = getBundleContent(data);
         }
         BundleFile result = null;
         // Ask factories before doing the default behavior
 BundleFileFactoryHook[] factories = adaptor.getHookRegistry().getBundleFileFactoryHooks();
         for (int i = 0; i < factories.length && result == null; i++)
             result = factories[i].createBundleFile(content, data, base);

         // No factories configured or they declined to create the bundle file; do default
 if (result == null && content instanceof File) {
             File file = (File) content;
             if (file.isDirectory())
                 result = new DirBundleFile(file);
             else
                 result = new ZipBundleFile(file, data);
         }

         if (result == null && content instanceof String ) {
             // here we assume the content is a path offset into the base bundle file; create a NestedDirBundleFile
 result = new NestedDirBundleFile(data.getBundleFile(), (String ) content);
         }
         if (result == null)
             // nothing we can do; must throw exception for the content
 throw new IOException("Cannot create bundle file for content of type: " + content.getClass().getName()); //$NON-NLS-1$

         // try creating a wrapper bundlefile out of it.
 BundleFileWrapperFactoryHook[] wrapperFactories = adaptor.getHookRegistry().getBundleFileWrapperFactoryHooks();
         for (int i = 0; i < wrapperFactories.length; i++) {
             BundleFile wrapperBundle = wrapperFactories[i].wrapBundleFile(result, content, data, base);
             if (wrapperBundle != null)
                 result = wrapperBundle;
         }
         return result;
     }

     public synchronized StateManager getStateManager() {
         if (stateManager != null)
             return stateManager;
         stateManager = readStateData();
         checkSystemState(stateManager.getSystemState());
         return stateManager;
     }

     private void checkSystemState(State state) {
         BundleDescription[] bundles = state.getBundles();
         if (bundles == null)
             return;
         boolean removedBundle = false;
         for (int i = 0; i < bundles.length; i++) {
             if (context.getBundle(bundles[i].getBundleId()) == null) {
                 state.removeBundle(bundles[i]);
                 removedBundle = true;
             }
         }
         if (removedBundle)
             state.resolve(false); // do a full resolve
 BundleDescription systemBundle = state.getBundle(0);
         if (systemBundle == null || !systemBundle.isResolved()) {
             ResolverError[] errors = systemBundle == null ? new ResolverError[0] : state.getResolverErrors(systemBundle);
             StringBuffer sb = new StringBuffer ();
             for (int i = 0; i < errors.length; i++) {
                 sb.append(errors[i].toString());
                 if (i < errors.length - 1)
                     sb.append(", "); //$NON-NLS-1$
 }
             // this would be a bug in the framework
 throw new IllegalStateException (NLS.bind(AdaptorMsg.SYSTEMBUNDLE_NOTRESOLVED, sb.toString()));
         }
     }

     private StateManager readStateData() {
         File[] stateFiles = findStorageFiles(new String [] {LocationManager.STATE_FILE, LocationManager.LAZY_FILE});
         File stateFile = stateFiles[0];
         File lazyFile = stateFiles[1];

         stateManager = new StateManager(stateFile, lazyFile, context, timeStamp);
         State systemState = null;
         if (!invalidState) {
             systemState = stateManager.readSystemState();
             if (systemState != null)
                 return stateManager;
         }
         systemState = stateManager.createSystemState();
         Bundle[] installedBundles = context.getBundles();
         if (installedBundles == null)
             return stateManager;
         StateObjectFactory factory = stateManager.getFactory();
         for (int i = 0; i < installedBundles.length; i++) {
             AbstractBundle toAdd = (AbstractBundle) installedBundles[i];
             try {
                 // make sure we get the real manifest as if this is the first time.
 Dictionary toAddManifest = loadManifest((BaseData) toAdd.getBundleData(), true);
                 BundleDescription newDescription = factory.createBundleDescription(systemState, toAddManifest, toAdd.getLocation(), toAdd.getBundleId());
                 systemState.addBundle(newDescription);
             } catch (BundleException be) {
                 // just ignore bundle datas with invalid manifests
 }
         }
         // we do not set the cached timestamp here because we want a new one to be used from the new system state object (bug 132978)
 // we need the state resolved
 systemState.resolve();
         invalidState = false;
         return stateManager;
     }

     private File[] findStorageFiles(String [] fileNames) {
         File[] storageFiles = new File[fileNames.length];
         try {
             for (int i = 0; i < storageFiles.length; i++)
                 storageFiles[i] = storageManager.lookup(fileNames[i], false);
         } catch (IOException ex) {
             if (Debug.DEBUG && Debug.DEBUG_GENERAL) {
                 Debug.println("Error reading state file " + ex.getMessage()); //$NON-NLS-1$
 Debug.printStackTrace(ex);
             }
         }
         boolean success = true;
         for (int i = 0; i < storageFiles.length; i++)
             if (storageFiles[i] == null || !storageFiles[i].isFile()) {
                 success = false;
                 break;
             }
         if (success)
             return storageFiles;
         //if it does not exist, try to read it from the parent
 Location parentConfiguration = null;
         Location currentConfiguration = LocationManager.getConfigurationLocation();
         if (currentConfiguration != null && (parentConfiguration = currentConfiguration.getParentLocation()) != null) {
             try {
                 File stateLocationDir = new File(parentConfiguration.getURL().getFile(), FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME);
                 StorageManager newFileManager = initFileManager(stateLocationDir, "none", true); //$NON-NLS-1$);
 for (int i = 0; i < storageFiles.length; i++)
                     storageFiles[i] = newFileManager.lookup(fileNames[i], false);
                 newFileManager.close();
             } catch (IOException ex) {
                 if (Debug.DEBUG && Debug.DEBUG_GENERAL) {
                     Debug.println("Error reading state file " + ex.getMessage()); //$NON-NLS-1$
 Debug.printStackTrace(ex);
                 }
             }
         } else {
             try {
                 //it did not exist in either place, so create it in the original location
 if (!isReadOnly()) {
                     for (int i = 0; i < storageFiles.length; i++)
                         storageFiles[i] = storageManager.lookup(fileNames[i], true);
                 }
             } catch (IOException ex) {
                 if (Debug.DEBUG && Debug.DEBUG_GENERAL) {
                     Debug.println("Error reading state file " + ex.getMessage()); //$NON-NLS-1$
 Debug.printStackTrace(ex);
                 }
             }
         }
         return storageFiles;
     }

     public void frameworkStart(BundleContext fwContext) throws BundleException {
         this.context = fwContext;
         // System property can be set to enable state saver or not.
 if (Boolean.valueOf(FrameworkProperties.getProperty(BaseStorage.PROP_ENABLE_STATE_SAVER, "true")).booleanValue()) //$NON-NLS-1$
 stateSaver = new StateSaver();

     }

     public void frameworkStop(BundleContext fwContext) throws BundleException {
         if (stateSaver != null)
             stateSaver.shutdown();
         saveAllData(true);
         storageManager.close();
         storageManagerClosed = true;
         if (extensionListener != null)
             context.removeBundleListener(extensionListener);
     }

     public void frameworkStopping(BundleContext fwContext) {
         // do nothing in storage
 }

     public void addProperties(Properties properties) {
         // set the extension support if we found the addURL method
 if (addURLMethod != null)
             properties.put(Constants.SUPPORTS_FRAMEWORK_EXTENSION, "true"); //$NON-NLS-1$
 // store bundleStore back into adaptor properties for others to see
 properties.put(BaseStorage.PROP_BUNDLE_STORE, getBundleStoreRoot().getAbsolutePath());
     }

     private InputStream findStorageStream(String fileName) {
         InputStream storageStream = null;
         try {
             storageStream = storageManager.getInputStream(fileName);
         } catch (IOException ex) {
             if (Debug.DEBUG && Debug.DEBUG_GENERAL) {
                 Debug.println("Error reading framework metadata: " + ex.getMessage()); //$NON-NLS-1$
 Debug.printStackTrace(ex);
             }
         }
         if (storageStream == null) {
             Location currentConfiguration = LocationManager.getConfigurationLocation();
             Location parentConfiguration = null;
             if (currentConfiguration != null && (parentConfiguration = currentConfiguration.getParentLocation()) != null) {
                 try {
                     File bundledataLocationDir = new File(parentConfiguration.getURL().getFile(), FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME);
                     StorageManager newStorageManager = initFileManager(bundledataLocationDir, "none", true); //$NON-NLS-1$
 storageStream = newStorageManager.getInputStream(fileName);
                     newStorageManager.close();
                 } catch (MalformedURLException e1) {
                     // This will not happen since all the URLs are derived by us
 // and we are GODS!
 } catch (IOException e1) {
                     // That's ok we will regenerate the .bundleData
 }
             }
         }
         return storageStream;
     }

     protected void saveBaseData(BaseData bundledata, DataOutputStream out) throws IOException {
         StorageHook[] hooks = bundledata.getStorageHooks();
         out.writeInt(hooks.length);
         for (int i = 0; i < hooks.length; i++) {
             out.writeUTF((String ) hooks[i].getKey());
             hooks[i].save(out);
         }
     }

     protected BaseData loadBaseData(long id, DataInputStream in) throws IOException {
         BaseData result = new BaseData(id, adaptor);
         int numHooks = in.readInt();
         StorageHook[] hooks = new StorageHook[numHooks];
         for (int i = 0; i < numHooks; i++) {
             String hookKey = in.readUTF();
             StorageHook storageHook = (StorageHook) storageHooks.getByKey(hookKey);
             if (storageHook == null)
                 throw new IOException();
             hooks[i] = storageHook.load(result, in);
         }
         result.setStorageHooks(hooks);
         return result;
     }

     protected BaseData createBaseData(long id, String location) {
         BaseData result = new BaseData(id, adaptor);
         result.setLocation(location);
         return result;
     }

     public String getInstallPath() {
         return installPath;
     }

     private void cleanOSGiCache() {
         File osgiConfig = LocationManager.getOSGiConfigurationDir();
         if (!AdaptorUtil.rm(osgiConfig)) {
             // TODO log error?
 }
     }

     /**
      * Processes an extension bundle
      * @param bundleData the extension bundle data
      * @param type the type of extension bundle
      * @throws BundleException on any errors or if the extension bundle type is not supported
      */
     protected void processExtension(BaseData bundleData, byte type) throws BundleException {
         if ((bundleData.getType() & BundleData.TYPE_FRAMEWORK_EXTENSION) != 0) {
             validateExtension(bundleData);
             processFrameworkExtension(bundleData, type);
         } else if ((bundleData.getType() & BundleData.TYPE_BOOTCLASSPATH_EXTENSION) != 0) {
             validateExtension(bundleData);
             processBootExtension(bundleData, type);
         }
     }

     /**
      * Validates the extension bundle metadata
      * @param bundleData the extension bundle data
      * @throws BundleException if the extension bundle metadata is invalid
      */
     private void validateExtension(BundleData bundleData) throws BundleException {
         Dictionary extensionManifest = bundleData.getManifest();
         if (extensionManifest.get(Constants.IMPORT_PACKAGE) != null)
             throw new BundleException(NLS.bind(AdaptorMsg.ADAPTOR_EXTENSION_IMPORT_ERROR, bundleData.getLocation()));
         if (extensionManifest.get(Constants.REQUIRE_BUNDLE) != null)
             throw new BundleException(NLS.bind(AdaptorMsg.ADAPTOR_EXTENSION_REQUIRE_ERROR, bundleData.getLocation()));
         if (extensionManifest.get(Constants.BUNDLE_NATIVECODE) != null)
             throw new BundleException(NLS.bind(AdaptorMsg.ADAPTOR_EXTENSION_NATIVECODE_ERROR, bundleData.getLocation()));
     }

     /**
      * Processes a framework extension bundle
      * @param bundleData the extension bundle data
      * @param type the type of extension bundle
      * @throws BundleException on errors or if framework extensions are not supported
      */
     protected void processFrameworkExtension(BaseData bundleData, byte type) throws BundleException {
         if (addURLMethod == null)
             throw new BundleException("Framework extensions are not supported.", new UnsupportedOperationException ()); //$NON-NLS-1$
 if ((type & (EXTENSION_UNINSTALLED | EXTENSION_UPDATED)) != 0)
             // if uninstalled or updated then do nothing framework must be restarted.
 return;

         // first make sure this BundleData is not on the pre-configured osgi.framework.extensions list
 String [] extensions = getConfiguredExtensions();
         for (int i = 0; i < extensions.length; i++)
             if (extensions[i].equals(bundleData.getSymbolicName()))
                 return;
         if ((type & EXTENSION_INSTALLED) != 0) {
             if (extensionListener == null) {
                 // add bundle listener to wait for extension to be resolved
 extensionListener = this;
                 context.addBundleListener(extensionListener);
             }
             return;
         }
         File[] files = getExtensionFiles(bundleData);
         if (files == null)
             return;
         ClassLoader cl = getClass().getClassLoader();
         for (int i = 0; i < files.length; i++) {
             if (files[i] == null)
                 continue;
             Throwable exceptionLog = null;
             try {
                 addURLMethod.invoke(cl, new Object [] {files[i].toURL()});
             } catch (InvocationTargetException e) {
                 exceptionLog = e.getTargetException();
             } catch (Throwable t) {
                 exceptionLog = t;
             } finally {
                 if (exceptionLog != null)
                     adaptor.getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, bundleData.getBundle(), exceptionLog);
             }
         }
         try {
             cl.loadClass("thisIsNotAClass"); // initialize the new urls //$NON-NLS-1$
 } catch (ClassNotFoundException e) {
             // do nothing
 }
     }

     /**
      * Returns a list of configured extensions
      * @return a list of configured extensions
      */
     protected String [] getConfiguredExtensions() {
         if (configuredExtensions != null)
             return configuredExtensions;
         String prop = FrameworkProperties.getProperty(BaseStorage.PROP_FRAMEWORK_EXTENSIONS);
         if (prop == null || prop.trim().length() == 0)
             configuredExtensions = new String [0];
         else
             configuredExtensions = ManifestElement.getArrayFromList(prop);
         return configuredExtensions;
     }

     /**
      * Processes a boot extension bundle
      * @param bundleData the extension bundle data
      * @param type the type of extension bundle
      * @throws BundleException on errors or if boot extensions are not supported
      */
     protected void processBootExtension(BundleData bundleData, byte type) throws BundleException {
         throw new BundleException("Boot classpath extensions are not supported.", new UnsupportedOperationException ()); //$NON-NLS-1$
 }

     private void initBundleStoreRoot() {
         File configurationLocation = LocationManager.getOSGiConfigurationDir();
         if (configurationLocation != null)
             bundleStoreRoot = new File(configurationLocation, LocationManager.BUNDLES_DIR);
         else
             // last resort just default to "bundles"
 bundleStoreRoot = new File(LocationManager.BUNDLES_DIR);
     }

     public File getBundleStoreRoot() {
         if (bundleStoreRoot == null)
             initBundleStoreRoot();
         return bundleStoreRoot;
     }

     /**
      * Returns a list of classpath files for an extension bundle
      * @param bundleData the bundle data for an extension bundle
      * @return a list of classpath files for an extension bundle
      */
     protected File[] getExtensionFiles(BaseData bundleData) {
         File[] files = null;
         try {
             String [] paths = bundleData.getClassPath();
             // TODO need to be smarter about dev path here
 if (FrameworkProperties.getProperty("osgi.dev") != null) { //$NON-NLS-1$
 String [] origPaths = paths;
                 paths = new String [origPaths.length + 1];
                 System.arraycopy(origPaths, 0, paths, 0, origPaths.length);
                 paths[paths.length - 1] = "bin"; //$NON-NLS-1$
 }
            ArrayList results = new ArrayList(paths.length);
            for (int i = 0; i < paths.length; i++) {
                if (".".equals(paths[i])) //$NON-NLS-1$
 results.add(bundleData.getBundleFile().getBaseFile());
                else {
                    File result = bundleData.getBundleFile().getFile(paths[i], false);
                    if (result != null)
                        results.add(result);
                }
            }
            return (File[]) results.toArray(new File[results.size()]);
        } catch (BundleException e) {
            adaptor.getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, bundleData.getBundle(), e);
        }
        return files;
    }

    void requestSave() {
        // Only when the State saver is enabled will the stateSaver be started.
 if (stateSaver == null)
            return;
        stateSaver.requestSave();
    }

    /**
     * Updates the state mananager with an updated/installed/uninstalled bundle
     * @param bundleData the modified bundle
     * @param type the type of modification
     * @throws BundleException
     */
    public void updateState(BundleData bundleData, int type) throws BundleException {
        if (stateManager == null) {
            invalidState = true;
            return;
        }
        State systemState = stateManager.getSystemState();
        switch (type) {
            case BundleEvent.UPDATED :
                systemState.removeBundle(bundleData.getBundleID());
                // fall through to INSTALLED
 case BundleEvent.INSTALLED :
                BundleDescription newDescription = stateManager.getFactory().createBundleDescription(systemState, bundleData.getManifest(), bundleData.getLocation(), bundleData.getBundleID());
                systemState.addBundle(newDescription);
                break;
            case BundleEvent.UNINSTALLED :
                systemState.removeBundle(bundleData.getBundleID());
                break;
        }
    }

    private static Method findaddURLMethod(Class clazz) {
        if (clazz == null)
            return null; // ends the recursion when getSuperClass returns null
 try {
            Method result = clazz.getDeclaredMethod("addURL", new Class [] {URL.class}); //$NON-NLS-1$
 result.setAccessible(true);
            return result;
        } catch (NoSuchMethodException e) {
            // do nothing look in super class below
 } catch (SecurityException e) {
            // if we do not have the permissions then we will not find the method
 }
        return findaddURLMethod(clazz.getSuperclass());
    }

    private class StateSaver implements Runnable {
        private long delay_interval = 30000; // 30 seconds.
 private long max_total_delay_interval = 1800000; // 30 minutes.
 private boolean shutdown = false;
        private long lastSaveTime = 0;
        private Thread runningThread = null;

        StateSaver() {
            String prop = FrameworkProperties.getProperty("eclipse.stateSaveDelayInterval"); //$NON-NLS-1$
 if (prop != null) {
                try {
                    long val = Long.parseLong(prop);
                    if (val >= 1000 && val <= 1800000) {
                        delay_interval = val;
                        max_total_delay_interval = val * 60;
                    }
                } catch (NumberFormatException e) {
                    // ignore
 }
            }
        }

        public void run() {
            State systemState = adaptor.getState();
            synchronized (systemState) {
                long firstSaveTime = lastSaveTime;
                long curSaveTime = 0;
                long delayTime;
                do {
                    do {
                        if ((System.currentTimeMillis() - firstSaveTime) > max_total_delay_interval) {
                            curSaveTime = lastSaveTime;
                            // Waiting time has been too long, so break to start saving State data to file.
 break;
                        }
                        delayTime = Math.min(delay_interval, lastSaveTime - curSaveTime);
                        curSaveTime = lastSaveTime;
                        // wait for other save requests
 try {
                            if (!shutdown)
                                systemState.wait(delayTime);
                        } catch (InterruptedException ie) {
                            // force break from do/while loops
 curSaveTime = lastSaveTime;
                            break;
                        }
                        // Continue the loop if 'lastSaveTime' is increased again during waiting.
 } while (!shutdown && curSaveTime < lastSaveTime);
                    // Save State and Meta data.
 saveAllData(false);
                    // Continue the loop if Saver is asked again during saving State data to file.
 } while (!shutdown && curSaveTime < lastSaveTime);
                runningThread = null; // clear runningThread
 }
        }

        void shutdown() {
            State systemState = adaptor.getState();
            Thread joinWith = null;
            synchronized (systemState) {
                shutdown = true;
                joinWith = runningThread;
                systemState.notifyAll(); // To wakeup sleeping thread.
 }
            try {
                if (joinWith != null)
                    // There should be no deadlock when 'shutdown' is true.
 joinWith.join();
            } catch (InterruptedException ie) {
                if (Debug.DEBUG && Debug.DEBUG_GENERAL) {
                    Debug.println("Error shutdowning StateSaver: " + ie.getMessage()); //$NON-NLS-1$
 Debug.printStackTrace(ie);
                }
            }
        }

        void requestSave() {
            State systemState = adaptor.getState();
            synchronized (systemState) {
                lastSaveTime = System.currentTimeMillis();
                if (runningThread == null) {
                    runningThread = new Thread (this, "State Saver"); //$NON-NLS-1$
 runningThread.start();
                }
            }
        }
    }

    public long getNextBundleId() {
        return nextId++;
    }

    public void bundleChanged(BundleEvent event) {
        if (event.getType() != BundleEvent.RESOLVED)
            return;
        BaseData data = (BaseData) ((AbstractBundle) event.getBundle()).getBundleData();
        try {
        if ((data.getType() & BundleData.TYPE_FRAMEWORK_EXTENSION) != 0)
            processFrameworkExtension(data, EXTENSION_INITIALIZE);
        else if ((data.getType() & BundleData.TYPE_BOOTCLASSPATH_EXTENSION) != 0)
            processBootExtension(data, EXTENSION_INITIALIZE);
        } catch (BundleException e) {
            // do nothing;
 }
    }

    public String copyToTempLibrary(BaseData data, String absolutePath) throws IOException {
        File storageRoot = getBundleStoreRoot();
        File libTempDir = new File(storageRoot, LIB_TEMP);
        // we assume the absolutePath is a File path
 File realLib = new File(absolutePath);
        String libName = realLib.getName();
        // find a temp dir for the bundle data and the library;
 File bundleTempDir = null;
        File libTempFile = null;
        // We need a somewhat predictable temp dir for the libraries of a given bundle;
 // This is not strictly necessary but it does help scenarios where one native library loads another native library without using java.
 // On some OSes this causes issues because the second library is cannot be found.
 // This has been worked around by the bundles loading the libraries in a particular order (and setting some LIB_PATH env).
 // The one catch is that the libraries need to be in the same directory and they must use their original lib names.
 //
 // This bit of code attempts to do that by using the bundle ID as an ID for the temp dir along with an incrementing ID
 // in cases where the temp dir may already exist.
 Long bundleID = new Long (data.getBundleID());
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            bundleTempDir = new File(libTempDir, bundleID.toString() + "_" + new Integer (i).toString()); //$NON-NLS-1$
 libTempFile = new File(bundleTempDir, libName);
            if (bundleTempDir.exists()) {
                if (libTempFile.exists())
                    continue; // to to next temp file
 break;
            }
            break;
        }
        if (!bundleTempDir.exists()) {
            bundleTempDir.mkdirs();
            bundleTempDir.deleteOnExit();
            // This is just a safeguard incase the VM is terminated unexpectantly, it also looks like deleteOnExit cannot really work because
 // the VM likely will still have a lock on the lib file at the time of VM exit.
 File deleteFlag = new File(libTempDir, BaseStorage.DELETE_FLAG);
            if (!deleteFlag.exists()) {
                // need to create a delete flag to force removal the temp libraries
 try {
                    FileOutputStream out = new FileOutputStream(deleteFlag);
                    out.close();
                } catch (IOException e) {
                    // do nothing; that would mean we did not make the temp dir successfully
 }
            }
        }
        // copy the library file
 InputStream in = new FileInputStream(realLib);
        AdaptorUtil.readFile(in, libTempFile);
        // set permissions if needed
 BundleFile.setPermissions(libTempFile);
        libTempFile.deleteOnExit(); // this probably will not work because the VM will probably have the lib locked at exit
 // return the temporary path
 return libTempFile.getAbsolutePath();
    }

}

